---
title: useReducer Hook
description: "Details about the useReducer React hook in ReScript"
canonical: "/docs/react/hooks-reducer"
section: "Hooks & State Management"
order: 4
---

# useReducer

<Intro>

`React.useReducer` helps you express your state in an action / reducer pattern.

</Intro>

## Usage

<CodeTab labels={["ReScript", "JS Output"]}>

```res
let (state, dispatch) = React.useReducer(reducer, initialState)
```

```js
var match = React.useReducer(reducer, initialState);
```

</CodeTab>

An alternative to [useState](./hooks-state.mdx). Accepts a reducer of type `(state, action) => newState`, and returns the current `state` paired with a `dispatch` function `(action) => unit`.

`React.useReducer` is usually preferable to `useState` when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. `useReducer` also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

Learn more about [records](../manual/record.mdx), [variants](../manual/variant.mdx), and [pattern matching & destructuring](../manual/pattern-matching-destructuring.mdx).

## Examples

### Counter Example with `React.useReducer`

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
// Counter.res

type action = Inc | Dec
type state = {count: int}

let reducer = (state, action) => {
  switch action {
  | Inc => {count: state.count + 1}
  | Dec => {count: state.count - 1}
  }
}

@react.component
let make = () => {
  let (state, dispatch) = React.useReducer(reducer, {count: 0})

  <>
    {React.string("Count:" ++ Belt.Int.toString(state.count))}
    <button onClick={(_) => dispatch(Dec)}> {React.string("-")} </button>
    <button onClick={(_) => dispatch(Inc)}> {React.string("+")} </button>
  </>
}
```

```js
function reducer(state, action) {
  if (action) {
    return {
      count: (state.count - 1) | 0,
    };
  } else {
    return {
      count: (state.count + 1) | 0,
    };
  }
}

function Counter(Props) {
  var match = React.useReducer(reducer, {
    count: 0,
  });
  var dispatch = match[1];
  return React.createElement(
    React.Fragment,
    undefined,
    "Count:" + String(match[0].count),
    React.createElement(
      "button",
      {
        onClick: function (param) {
          return Curry._1(dispatch, /* Dec */ 1);
        },
      },
      "-",
    ),
    React.createElement(
      "button",
      {
        onClick: function (param) {
          return Curry._1(dispatch, /* Inc */ 0);
        },
      },
      "+",
    ),
  );
}
```

</CodeTab>

> React guarantees that dispatch function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.

### Basic Todo List App with More Complex Actions

You can leverage the full power of variants to express actions with data payloads to parametrize your state transitions:

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
// TodoApp.res

type todo = {
  id: int,
  content: string,
  completed: bool,
}

type action =
  | AddTodo(string)
  | RemoveTodo(int)
  | ToggleTodo(int)

type state = {
  todos: array<todo>,
  nextId: int,
}

let reducer = (state, action) =>
  switch action {
  | AddTodo(content) =>
    let todos = Array.concat(
      state.todos,
      [{id: state.nextId, content: content, completed: false}],
    )
    {todos: todos, nextId: state.nextId + 1}
  | RemoveTodo(id) =>
    let todos = Array.filter(state.todos, todo => todo.id !== id)
    {...state, todos: todos}
  | ToggleTodo(id) =>
    let todos = Belt.Array.map(state.todos, todo =>
      if todo.id === id {
        {
          ...todo,
          completed: !todo.completed,
        }
      } else {
        todo
      }
    )
    {...state, todos: todos}
  }

let initialTodos = [{id: 1, content: "Try ReScript & React", completed: false}]

@react.component
let make = () => {
  let (state, dispatch) = React.useReducer(
    reducer,
    {todos: initialTodos, nextId: 2},
  )

  let todos = Belt.Array.map(state.todos, todo =>
    <li>
      {React.string(todo.content)}
      <button onClick={_ => dispatch(RemoveTodo(todo.id))}>
        {React.string("Remove")}
      </button>
      <input
        type_="checkbox"
        checked=todo.completed
        onChange={_ => dispatch(ToggleTodo(todo.id))}
      />
    </li>
  )

  <> <h1> {React.string("Todo List:")} </h1> <ul> {React.array(todos)} </ul> </>
}
```

```js
function reducer(state, action) {
  switch (action.TAG | 0) {
    case /* AddTodo */ 0:
      var todos = state.todos.concat([
        {
          id: state.nextId,
          content: action._0,
          completed: false,
        },
      ]);
      return {
        todos: todos,
        nextId: (state.nextId + 1) | 0,
      };
    case /* RemoveTodo */ 1:
      var id = action._0;
      var todos$1 = state.todos.filter(function (todo) {
        return todo.id !== id;
      });
      return {
        todos: todos$1,
        nextId: state.nextId,
      };
    case /* ToggleTodo */ 2:
      var id$1 = action._0;
      var todos$2 = Belt_Array.map(state.todos, function (todo) {
        if (todo.id === id$1) {
          return {
            id: todo.id,
            content: todo.content,
            completed: !todo.completed,
          };
        } else {
          return todo;
        }
      });
      return {
        todos: todos$2,
        nextId: state.nextId,
      };
  }
}

var initialTodos = [
  {
    id: 1,
    content: "Try ReScript & React",
    completed: false,
  },
];

function TodoApp(Props) {
  var match = React.useReducer(reducer, {
    todos: initialTodos,
    nextId: 2,
  });
  var dispatch = match[1];
  var todos = Belt_Array.map(match[0].todos, function (todo) {
    return React.createElement(
      "li",
      undefined,
      todo.content,
      React.createElement(
        "button",
        {
          onClick: function (param) {
            return Curry._1(dispatch, {
              TAG: /* RemoveTodo */ 1,
              _0: todo.id,
            });
          },
        },
        "Remove",
      ),
      React.createElement("input", {
        checked: todo.completed,
        type: "checkbox",
        onChange: function (param) {
          return Curry._1(dispatch, {
            TAG: /* ToggleTodo */ 2,
            _0: todo.id,
          });
        },
      }),
    );
  });
  return React.createElement(
    React.Fragment,
    undefined,
    React.createElement("h1", undefined, "Todo List:"),
    React.createElement("ul", undefined, todos),
  );
}
```

</CodeTab>

## Lazy Initialization

<CodeTab labels={["ReScript", "JS Output"]}>

```res
let (state, dispatch) =
  React.useReducerWithMapState(reducer, initialState, initial)
```

```js
var match = React.useReducer(reducer, initialState, init);
```

</CodeTab>

You can also create the `initialState` lazily. To do this, you can use `React.useReducerWithMapState` and pass an `init` function as the third argument. The initial state will be set to `init(initialState)`.

It lets you extract the logic for calculating the initial state outside the reducer. This is also handy for resetting the state later in response to an action:

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
// Counter.res

type action = Inc | Dec | Reset(int)
type state = {count: int}

let init = initialCount => {
  {count: initialCount}
}

let reducer = (state, action) => {
  switch action {
  | Inc => {count: state.count + 1}
  | Dec => {count: state.count - 1}
  | Reset(count) => init(count)
  }
}

@react.component
let make = (~initialCount: int) => {
  let (state, dispatch) = React.useReducerWithMapState(
    reducer,
    initialCount,
    init,
  )

  <>
    {React.string("Count:" ++ Belt.Int.toString(state.count))}
    <button onClick={_ => dispatch(Dec)}> {React.string("-")} </button>
    <button onClick={_ => dispatch(Inc)}> {React.string("+")} </button>
  </>
}
```

```js
function init(initialCount) {
  return {
    count: initialCount,
  };
}

function reducer(state, action) {
  if (typeof action === "number") {
    if (action !== 0) {
      return {
        count: (state.count - 1) | 0,
      };
    } else {
      return {
        count: (state.count + 1) | 0,
      };
    }
  } else {
    return {
      count: action._0,
    };
  }
}

function Counter(Props) {
  var initialCount = Props.initialCount;
  var match = React.useReducer(reducer, initialCount, init);
  var dispatch = match[1];
  return React.createElement(
    React.Fragment,
    undefined,
    "Count:" + String(match[0].count),
    React.createElement(
      "button",
      {
        onClick: function (param) {
          return Curry._1(dispatch, /* Dec */ 1);
        },
      },
      "-",
    ),
    React.createElement(
      "button",
      {
        onClick: function (param) {
          return Curry._1(dispatch, /* Inc */ 0);
        },
      },
      "+",
    ),
  );
}
```

</CodeTab>
